package com.dynamic.mybatis.core.parser.field;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.dynamic.mybatis.core.enums.CommandType;
import com.dynamic.mybatis.core.enums.ReturnType;
import com.dynamic.mybatis.core.enums.Type;
import com.dynamic.mybatis.core.metadata.*;
import com.dynamic.mybatis.core.session.DynamicSqlSession;
import com.dynamic.mybatis.core.utils.BeanUtils;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.w3c.dom.NodeList;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author xs
 * @Date 2022/11/17 11:05
 */
public abstract class DynamicMappedStatementInputParameterFieldParser extends DynamicMappedStatementOutputParameterFieldParser {

    private static Map<String, Method> nodeHandlerMethodMap = new HashMap<>();
    private static Map<String, Object> nodeHandlerMap;
    /**
     * #{} 或 ${} 参数解析
     */
    List<Pattern> patterns = new ArrayList(){{
        add(Pattern.compile("#\\{[\\S]*\\}|\\$\\{[\\S]*\\}"));
    }};


    public DynamicMappedStatementInputParameterFieldParser(DynamicSqlSession dynamicSqlSession) {
        super(dynamicSqlSession);
        try {
            XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(getConfiguration(), null);
            Field field = XMLScriptBuilder.class.getDeclaredField("nodeHandlerMap");
            field.setAccessible(true);
            nodeHandlerMap = (Map<String, Object>) field.get(xmlScriptBuilder);
            for (Map.Entry<String, Object> entry : nodeHandlerMap.entrySet()) {
                Method method = entry.getValue().getClass().getMethod("handleNode", XNode.class, List.class);
                method.setAccessible(true);
                nodeHandlerMethodMap.put(entry.getKey(), method);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public String getFirstPrimaryKeyPath(MappedStatementMetaField field) {
        if (field.isPrimaryKey() || "id".equals(field.getField())) {
            return field.getField();
        }
        if (CollectionUtils.isEmpty(field.getChildren())) {
            return "";
        }
        StringBuilder path = new StringBuilder();
        if (StringUtils.isNotBlank(field.getField())) {
            path.append(field.getField());
        }
        for (MappedStatementMetaField f : field.getChildren()) {
            String firstFieldKey = getFirstPrimaryKeyPath(f);
            if (StringUtils.isNotBlank(firstFieldKey)) {
                if (path.length() > 0) {
                    path.append(".");
                }
                path.append(firstFieldKey);
                return path.toString();
            }
        }
        return path.toString();
    }


    public MappedStatementMetaField inputParameterFieldParser(List<MappedStatementMetaField> databaseFields, DynamicMappedStatement dms, boolean wrapper){
        Element element = dynamicSqlSession.getDynamicMappedStatementHelper().getMappedStatementParser().parserDom4jMapperBodyXml(dms.getSqlStatement(), dms.getNamespace());
        //  XNode xNodes =  mappedStatementParser.parserMapperBodyXml(mappedStatement.getMappedStatement(),mappedStatement.getNamespace());
        // List<DynamicSqlNode> sqlNodeTree =  this.parseSqlNodeTree(xNodes,false,"","");
        List<DynamicSqlNode> sqlNodeTree = this.parseSqlNodeTreeByDom4j(element, false, "", "");
        sqlNodeTree = this.filterEmptyPathNode(sqlNodeTree);
        List<ParameterPath> paths = this.getParameterPaths(sqlNodeTree);
        if (StringUtils.isNotBlank(dms.getParameterWrapper()) && wrapper) {
            if (dms.isBatch()) {
                paths = paths.stream().peek(p -> p.path = dms.getParameterWrapper() + "[]." + p.path).collect(Collectors.toList());
            } else {
                paths = paths.stream().peek(p -> p.path = dms.getParameterWrapper() + "." + p.path).collect(Collectors.toList());
            }
        }
        MappedStatementMetaField field = this.buildMappedStatementField(paths);
        field = formatMappedStatementParameterField(field, dms, wrapper);
        //建数据库字段合并到参数集
        mergeDataBseToJavaBatch(databaseFields, field.getChildren(), dms.getCommandType() == CommandType.StandardCommand.SELECT);
        return field;
    }

    //特殊结果集处理
    public MappedStatementMetaField formatMappedStatementParameterField(MappedStatementMetaField field, DynamicMappedStatement dms, boolean wrapper) {

        if (dms.isBatch() && StringUtils.isBlank(dms.getParameterWrapper())) {
            field.setType(Type.StandardType.ARRAY.javaTypeAlias);
        }
        //合并去重
        field = distinctMergeChildrenTree(field);
        if (dms.getReturnType() == ReturnType.StandardReturnType.PAGE) {
            MappedStatementMetaField pageNumberField = new MappedStatementMetaField();
            pageNumberField.setType(Type.StandardType.BIGINT.javaTypeAlias);
            pageNumberField.setName("当前页");
            pageNumberField.setField(DynamicPage.PAGE_NUMBER);
            pageNumberField.setVisible(Boolean.TRUE);
            pageNumberField.setDefaultValue(1);
            MappedStatementFieldValidator pageNumberFieldValidator = new MappedStatementFieldValidator();
            pageNumberFieldValidator.setMin(1);

            pageNumberFieldValidator.setUnsigned(Boolean.TRUE);
            pageNumberField.setValidator(pageNumberFieldValidator);
            field.getChildren().add(pageNumberField);

            MappedStatementMetaField pageSizeField = new MappedStatementMetaField();
            pageSizeField.setType(Type.StandardType.BIGINT.javaTypeAlias);
            pageSizeField.setName("每页数量");
            pageSizeField.setField(DynamicPage.PAGE_SIZE);
            pageSizeField.setVisible(Boolean.TRUE);
            pageSizeField.setDefaultValue(10);
            MappedStatementFieldValidator pageSizeFieldValidator = new MappedStatementFieldValidator();
            pageSizeFieldValidator.setMin(1);

            pageSizeFieldValidator.setUnsigned(Boolean.TRUE);
            pageSizeField.setValidator(pageSizeFieldValidator);
            field.getChildren().add(pageSizeField);
        }
        // MappedStatement mappedStatement = configuration.getMappedStatement(dms.getDynamicMappedStatementId());
        //  mappedStatement.getParameterMap();
        // mappedStatementField.setName(dms.getDynamicMappedStatementId());
        if (StringUtils.isBlank(field.getType())) {
            field.setType(Type.StandardType.STRUCT.javaTypeAlias);
        } else if (Type.StandardType.ARRAY.javaTypeAlias.equals(field.getType())) {
            field.setGenericType(Type.StandardType.STRUCT.javaTypeAlias);
        }
        if (StringUtils.isNotBlank(dms.getParameterWrapper()) && dms.isBatch() && wrapper
                && dms.getParameterWrapper().split("\\.").length == 1 && Type.StandardType.ARRAY.javaTypeAlias.equalsIgnoreCase(field.getChildren().get(0).getType())) {
            field.getChildren().get(0).setGenericType(Type.StandardType.STRUCT.javaTypeAlias);
        }
        return field;
    }


    private List<String> matcherParameter(String text) {
        List<String> paths = new ArrayList<>();
        for (Pattern pattern : patterns) {
            Matcher matcher = pattern.matcher(text);
            while (matcher.find()) {
                String p = matcher.group();
                String param = p.substring(2, p.length() - 1);
                paths.add(param);
            }
        }
        return paths;
    }

    private List<String> matcherQuoteReplace(String text, String key, String alias) {
        List<String> paths = matcherParameter(text);
        if (CollectionUtils.isEmpty(paths) || StringUtils.isBlank(key) || StringUtils.isBlank(alias)) {
            return paths;
        }
        for (int i = 0; i < paths.size(); i++) {
            String path = paths.get(i).replaceAll("\\s", "");
            if (path.equals(alias) || path.startsWith(alias + ".")) {
                path = path.replaceFirst(alias, key + "[]");
            }
            paths.set(i, path);
        }
        return paths;
    }


    //解析引用的标签   foreach  bind
    private List<DynamicSqlNode> parseSqlNodeTree(XNode rootXNode, boolean parentIf, String key, String alias) {
        List<DynamicSqlNode> dynamicSqlNodes = new ArrayList<>();
        List<XNode> children1 = rootXNode.getChildren();
        if (CollectionUtils.isEmpty(children1)) {
            children1 = new ArrayList<>();
            children1.add(rootXNode);
        }
        for (XNode xNode : children1) {
            NodeList children = xNode.getNode().getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                XNode newXNode = xNode.newXNode(children.item(i));
                String body = newXNode.getStringBody("").replaceAll("^\\s*|\\s*$", "");
                DynamicSqlNode dynamicSqlNode = new DynamicSqlNode();
                if (newXNode.getNode().hasChildNodes()) {
                    dynamicSqlNode.setChildren(parseSqlNodeTree(newXNode, parentIf, key, alias));
                } else if (StringUtils.isNotBlank(body)) {
                    if ("foreach".equals(xNode.getName())) {
                        key = xNode.getStringAttribute("collection");
                        alias = xNode.getStringAttribute("item");
                    }
                    parentIf = parentIf ? true : "if".equals(xNode.getName()) || "when".equals(xNode.getName());
                    dynamicSqlNode.setParentIf(parentIf);
                    dynamicSqlNode.setSqlNode(newXNode.getName());
                    String text = newXNode.getStringBody("");
                    List<String> paths = matcherQuoteReplace(text, key, alias);
                    dynamicSqlNode.setPaths(paths);
                }
                dynamicSqlNodes.add(dynamicSqlNode);
            }
        }
        return dynamicSqlNodes;
    }

    //解析引用的标签   foreach  bind
    private List<DynamicSqlNode> parseSqlNodeTreeByDom4j(Element rootElement, boolean parentIf, String key, String alias) {
        String tagXml = rootElement.asXML();
        List<DynamicSqlNode> dynamicSqlNodes = new ArrayList<>();
        List<Element> children = rootElement.elements();
        if (CollectionUtils.isEmpty(children)) {
            children = new ArrayList<>();
            children.add(rootElement);
        }
        for (Element element : children) {
            String tagName = element.getName();
            String body = element.getText().replaceAll("^\\s*|\\s*$", "");
            DynamicSqlNode dynamicSqlNode = new DynamicSqlNode();
            if (element.elements() != null && element.elements().size() > 0) {
                dynamicSqlNode.setChildren(parseSqlNodeTreeByDom4j(element, parentIf, key, alias));
            }
            if (StringUtils.isNotBlank(body)) {
                if ("foreach".equals(element.getName())) {
                    key = element.attributeValue("collection");
                    alias = element.attributeValue("item");
                }
                parentIf = parentIf ? true : "if".equals(element.getName()) || "when".equals(element.getName());
                dynamicSqlNode.setParentIf(parentIf);
                dynamicSqlNode.setSqlNode(element.getName());
                String text = element.getText();
                List<String> paths = matcherQuoteReplace(text, key, alias);
                dynamicSqlNode.setPaths(paths);
            }
            dynamicSqlNodes.add(dynamicSqlNode);
        }
        return dynamicSqlNodes;
    }


    //将没有入参的子节点丢弃
    private List<DynamicSqlNode> filterEmptyPathNode(List<DynamicSqlNode> dynamicSqlNodes) {
        List<DynamicSqlNode> dsnList = new ArrayList<>();
        if (CollectionUtils.isEmpty(dynamicSqlNodes)) {
            return dsnList;
        }
        for (DynamicSqlNode dsn : dynamicSqlNodes) {
            if (CollectionUtils.isNotEmpty(dsn.getPaths())) {
                dsnList.add(dsn);
            } else {
                dsnList.addAll(filterEmptyPathNode(dsn.getChildren()));
            }
        }
        return dsnList;
    }


    private List<ParameterPath> getParameterPaths(List<DynamicSqlNode> tree) {
        List<DynamicSqlNode> array = toArray(tree);
        List<ParameterPath> paths = new ArrayList<>();
        for (DynamicSqlNode dsn : array) {
            if (CollectionUtils.isNotEmpty(dsn.getPaths())) {
                paths.addAll(dsn.getPaths().stream().map(s -> new ParameterPath(s.split(",")[0], !dsn.isParentIf())).collect(Collectors.toList()));
            }
        }
        if (CollectionUtils.isEmpty(paths) || paths.size() <= 1) {
            return paths;
        }
        //去重
        for (int i = 0; i < paths.size(); i++) {
            for (int j = i + 1; j < paths.size() - 1; j++) {
                String before = paths.get(i).getPath();
                String after = paths.get(j).getPath();
                if (StringUtils.isNotBlank(before) && (before.startsWith(after + ".") || before.equals(after))) {
                    paths.get(i).required = (paths.get(i).required || paths.get(j).required);
                    paths.set(j, ParameterPath.empty);
                }
            }
        }
        return paths.stream().distinct().filter(p -> p != ParameterPath.empty).collect(Collectors.toList());
    }

    private List<DynamicSqlNode> toArray(List<DynamicSqlNode> tree) {
        List<DynamicSqlNode> array = new ArrayList<>();
        for (DynamicSqlNode dsn : tree) {
            array.add(dsn);
            if (CollectionUtils.isNotEmpty(dsn.getChildren())) {
                array.addAll(toArray(dsn.getChildren()));
            }
        }
        return array;
    }

    private MappedStatementMetaField buildMappedStatementField(List<ParameterPath> paths) {
        MappedStatementMetaField field = new MappedStatementMetaField();
        List<MappedStatementMetaField> children = new ArrayList<>();
        field.setChildren(children);
        for (ParameterPath pp : paths) {
            String path = pp.getPath();

            String[] array = path.split("\\.");
            Type firstType = array.length > 1 ? Type.StandardType.STRUCT : null;
            MappedStatementMetaField secondField = batchBuildMappedStatementField(new String[]{array[0]}, firstType, pp.required).get(0);
            children.add(secondField);
            Type netType = array.length > 2 ? Type.StandardType.STRUCT : null;
            secondField.setChildren(batchBuildMappedStatementField(Arrays.copyOfRange(array, 1, array.length), netType, pp.required));
        }
        return field;
    }


    private List<MappedStatementMetaField> batchBuildMappedStatementField(String[] paths, Type type, boolean required) {
        if (paths == null || paths.length < 1) {
            return null;
        }
        List<MappedStatementMetaField> fields = new ArrayList<>();
        MappedStatementMetaField field = new MappedStatementMetaField();
        String key = paths[0];
        if (key.endsWith("[]")) {
            field.setType(Type.StandardType.ARRAY.name().toLowerCase());
            key = key.substring(0, key.length() - 2);
        } else if (type != null) {
            field.setType(type.getJavaTypeAlias());
        }
        field.setName(key);
        field.setField(key);
        field.setVisible(Boolean.TRUE);
        MappedStatementFieldValidator validator = new MappedStatementFieldValidator();
        validator.setRequired(required);
        field.setValidator(validator);
        fields.add(field);
        if (paths.length > 1) {
            if (!field.getType().equalsIgnoreCase(Type.StandardType.STRUCT.javaTypeAlias)) {
                field.setGenericType(Type.StandardType.STRUCT.name().toLowerCase());
            }
            Type netType = paths.length > 2 ? Type.StandardType.STRUCT : null;
            field.setChildren(batchBuildMappedStatementField(Arrays.copyOfRange(paths, 1, paths.length), netType, required));
        }
        return fields;
    }

    static class ParameterPath {

        public ParameterPath(String path, boolean required) {
            this.path = path;
            this.required = required;
        }

        static ParameterPath empty = new ParameterPath("", false);
        String path;
        boolean required;

        public String getPath() {
            return path;
        }

        public boolean isRequired() {
            return required;
        }
    }


    public MappedStatementMetaField distinctMergeChildrenTree(MappedStatementMetaField field) {
        if (field.getChildren() == null || field.getChildren().size() < 1) {
            return field;
        }
        MappedStatementMetaField first = field.getChildren().get(0);
        for (int i = 1; i < field.getChildren().size(); i++) {
            mergeChildrenTree(first, field.getChildren().get(i));
        }
        //去重合并
        List<MappedStatementMetaField> children = field.getChildren().stream().collect(
                Collectors.collectingAndThen(Collectors.toCollection(() ->
                        new TreeSet<>(Comparator.comparing(f -> f.getField()))), ArrayList::new));
        field.setChildren(children);
        return field;
    }


    public void mergeChildrenTree(MappedStatementMetaField master, MappedStatementMetaField slave) {
        if (slave.getChildren() == null) {
            return;
        }
        for (MappedStatementMetaField slaveChild : slave.getChildren()) {
            if (master.getChildren() == null) {
                master.setChildren(new ArrayList<>());
            }
            Optional<MappedStatementMetaField> masterSameChild = master.getChildren().stream().filter(item -> slaveChild.getField().equals(item.getField())).findFirst();
            if (masterSameChild.isPresent()) {
                mergeChildrenTree(masterSameChild.get(), slaveChild);
            } else {//不存在就拼接
                master.getChildren().add(slaveChild);
            }
        }
    }

    public void mergeTree(MappedStatementMetaField master, MappedStatementMetaField slave) {
        if (slave == null) {
            return;
        } else if (CollectionUtils.isEmpty(slave.getChildren())) {
            if (master.getChildren() == null) {
                master.setChildren(new ArrayList<>());
            }
            if (!Objects.equals(master.getField(), slave.getField()) || (master.getField() == null && !Objects.equals(master.getType(), slave.getType()))) {
                Optional<MappedStatementMetaField> sameOptional = master.getChildren().stream().filter(m -> Objects.equals(m.getField(), slave.getField())).findFirst();
                if (sameOptional.isPresent()) {
                    MappedStatementMetaField same = sameOptional.get();
                    BeanUtils.copy(slave, same);
                    MappedStatementFieldValidator validator;
                    if (same.getValidator() != null) {
                        validator = BeanUtils.copy(slave.getValidator(), same.getValidator());
                    } else {
                        validator = slave.getValidator();
                    }
                    same.setValidator(validator);
                } else {
                    master.getChildren().add(slave);
                }
            } else if (Objects.equals(master.getField(), slave.getField()) && Objects.equals(master.getType(), slave.getType())) {
                BeanUtils.copy(slave, master);
            }
            return;
        }
        for (MappedStatementMetaField slaveChild : slave.getChildren()) {
            List<MappedStatementMetaField> masterChildren = master.getChildren();
            BeanUtils.copy(slave, master);
            master.setChildren(masterChildren);
            if (master.getChildren() == null) {
                master.setChildren(new ArrayList<>());
            }
            Optional<MappedStatementMetaField> masterSameChild = master.getChildren().stream().filter(item -> Objects.equals(slaveChild.getField(), item.getField())).findFirst();
            if (masterSameChild.isPresent()) {
                mergeTree(masterSameChild.get(), slaveChild);
            } else {//不存在就拼接
                master.getChildren().add(slaveChild);
            }
        }
    }


    public List<MappedStatementMetaField> mergeArray(List<MappedStatementMetaField> master, List<MappedStatementMetaField> slave) {
        for (MappedStatementMetaField masterField : master) {
            MappedStatementMetaField slaveField = slave.stream().filter(f -> Objects.equals(masterField.getField(), f.getField())).findFirst().orElse(null);
            if (slaveField == null) {
                continue;
            }
            MappedStatementFieldValidator validator;
            if (masterField.getValidator() != null) {
                validator = BeanUtils.copy(slaveField.getValidator(), masterField.getValidator());
            } else {
                validator = slaveField.getValidator();
            }
            BeanUtils.copy(slaveField, masterField);
            masterField.setValidator(validator);
        }
        return master;
    }

}